#include "freetypefont.h"

#include "woopsistring.h"
#include "stringiterator.h"

using namespace WoopsiUI;

FreeTypeFont::FreeTypeFont(FreeTypeCache* fontCache,const FaceId * faceId,const u8 height,const u8 fixedWidth) { // Don't need transparency

	_fontCache = fontCache;
	_scaler.pixel = 110; // DPI
	_scaler.x_res = 0; // Computations are done in pixels and not real units
	_scaler.y_res = 0; // Computations are done in pixels and not real unit
	setFont(fixedWidth,height,faceId);
}

void FreeTypeFont::getSbit(const u32 codepoint) const{
	_codepoint = codepoint;
	_fontCache->loadSbit(_scaler,((FaceId*)_scaler.face_id)->charMapIndex(),codepoint,&_sbit);
	if(!(((FaceId*)_scaler.face_id)->nMap()) ||(_sbit->width)) return;
	u32 tempCMap = ((FaceId*)_scaler.face_id)->charMapIndex();
	do{
		tempCMap ++;
		if(tempCMap >= ((FaceId*)_scaler.face_id)->nMap()) tempCMap = 0;
		if(tempCMap == ((FaceId*)_scaler.face_id)->charMapIndex()) return;
		_fontCache->loadSbit(_scaler,tempCMap,codepoint,&_sbit);
	}while(!_sbit->width);
}

u16 FreeTypeFont::getStringWidth(const WoopsiString& text) const{
	if((!((FaceId*)_scaler.face_id)->isValid())|| !text.getLength()) return 0;

	u32 width = 0;
	StringIterator* iterator = text.newStringIterator();

	do{
		u32 codepoint = iterator->getCodePoint();
		if((codepoint != 10) && (codepoint != 13)) width += getCharWidth(codepoint);
	}while(iterator->moveToNext());

	delete iterator;
	return width;
}

u16 FreeTypeFont::getStringWidth(const WoopsiString& text,s32 startIndex,s32 length) const{
	if((!((FaceId*)_scaler.face_id)->isValid())|| !length) return 0;
	StringIterator* iterator = text.newStringIterator();
	if(!iterator->moveTo(startIndex)){
		delete iterator;
		return 0;
	}

	u32 width = 0;
	do{
		u32 codepoint = iterator->getCodePoint();
		if((codepoint != 10) && (codepoint != 13)) width += getCharWidth(codepoint);
		length--;
	}while((iterator->moveToNext()) && (length > 0));

	delete iterator;
	return width;
}

/**
 The following array has been produced with the following Maple code :

 for a from 0 to 255 do
 L[a]:=0:
 for b from 0 to 31 do
 n:=(floor(a*b/255)-floor(a*b/256)):
 if (n>0) then L[a] := L[a] + 2^b; fi;
 od;
 od:
 seq(sprintf("0x%X%X",ceil(L[a]/2^16),modp(L[a],2^16)), a=0..255);


 The purpose of this array is that:
 if 0<=a<=255 and 0<=b<= 31 then a*b/255 = ((a*b) << 6) + ((transparencyJump[a] & Bit(b)) ? 1:0);

 The aim of this array is to replace a possibly costly division by 255 by a bit
 rotation and an addition.  The gain in speed depends on the arm assembly.

 It could be made even faster with an array of 256*32*1 byte = 8k, by replacing
 the multiplication and the division by an array lookup.

 by a*b/255 = result[a<<5+b]
 */

u32 transparencyJump[256] = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x40008000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
		0x20,0x0,0x0,0x80000,0x40008000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x20,0x0,0x0,0x0,0x0,0x0,0x42108420,0x0,0x0,0x0,0x0,0x0,0x0,0x400,
		0x4002000,0x20,0x0,0x0,0x0,0x0,0x0,0x80000,0x0,0x40008000,0x0,0x0,0x40,0x0,0x10204080,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x10000,0x0,0x0,0x49249248,0x0,
				0x0,0x20000,0x800,0x20,0x0,0x2000,0x40800,0x80,0x0,0x0,0x20000,0x0,0x80000,0x800,0x0,0x42108420,0x0,0x8000,0x20,0x0,0x80000,0x4000,0x0,0x0,
				0x800,0x0, 0x0,0x0,0x100,0x40800,0x1000,0x4002000,0x40008000,0x20,0x80,0x800,0x8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xA0000,0x2000,0x200,0x20,
				0x40008000,0x10000, 0x0,0x0,0x80000,0x20000,0x804200,0x2000,0x0,0x40000,0x10204080,0x0,0x80,0x1001000,0x20020,0x400,0x0,0x42108420,0x0,0x10000,
				0x40,0x4002000,0x200,0x0, 0x0,0x8000,0x40000,0x0,0x10004000,0x80020,0x100,0x4000,0x0,0x0,0x49249248,0x0,0x0,0x80000,0x2400,0x80,0x20000,
				0x4002000,0x800,0x4010400,0x8020, 0x81000,0x0,0x0,0x2000,0x20000,0x40800,0x44008000,0x80,0x8800,0x0,0x0,0x0,0x0,0x22000,0x220,0x40000,0x0,
				0x80000,0x804200,0x800,0x10004000,0x1080, 0x20000,0x42108420,0x0,0x84000,0x200,0x8000,0x40800,0x10020,0x800,0x0,0x41041040,0x80000,0x2080,
				0x4002000,0x100,0x8000,0x10204080,0x20000, 0x40408000,0x800,0x0,0x0,0x2020,0x4000,0x804200,0x10000,0x20080,0x4010400,0x80200,0x40800,0x800,
				0x1001000,0x2000,0x4002000,0x18004000, 0x60008000,0x80000,0x20,0x40,0x80,0x300,0xC00,0x3000,0x1C000,0xE0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
				0xFFFFFFFE };

/*
 void Graphics::drawText(s16 x, s16 y, FontBase* font, const WoopsiString& string, s32 startIndex, s32 length) draws a string by displaying :
 char1 at position (x + left1, y) 
 char 2 at position (x + advance1 + left2, y),
 char 3 at position (x + avance1 + advance2 + left3, y)
 ...
 
 void Graphics::drawText(s16 x, s16 y, FontBase* font, const WoopsiString& string, s32 startIndex, s32 length) draws a string by displaying :
 char 1 at position (x + left1, y - top1)
 char 2 at position (x + advance1 + left2, y - top2)
 char 3 at position (x + advance1 + advance2 + left3, y - top3)
 ...
 
 woopsi gadget basically draws fixed width and height
 
 */

s16 FreeTypeFont::drawBaselineChar(MutableBitmapBase * bitmap,u32 letter,u16 colour,s16 x,s16 y,u16 clipX1,u16 clipY1,u16 clipX2,u16 clipY2){
	if(!((FaceId*)_scaler.face_id)->isValid()) return x;

	// No need to draw blank or glyphs missing a bitmap
	switch(letter){
	case 10: // Line feed
	case 13: // Carriage return
		return x;
	default:
		break;
	}

	if(letter != _codepoint) getSbit(letter);
	
	s16 output = x + _sbit->xadvance;
	if(!_sbit->buffer) return output;
	
	u8 fontWidth = _sbit->width;
	u8 fontHeight = _sbit->height;

	u8 maxGrays = _sbit->max_grays; // should be 255
	if(!maxGrays) return output;

	// offsets for the glyph
	x += _sbit->left;
	y -= _sbit->top;

	// This should be done with the new Rect class, but let's get it working first
	// TODO (olivier.binda@wanadoo.fr)

	// no need to blit to screen
	if((clipX1 > clipX2) || (clipY1 > clipY2) || (fontWidth <=0) || (fontHeight <=0)) return output;
	if(y > clipY2) return output;
	if(y + fontHeight - 1 < clipY1) return output;
	if(x > clipX2) return output;
	if(x + fontWidth -1 < clipX1) return output;

	// Calculate values for clipping
	s16 startX = x > clipX1?x:clipX1;
	s16 endX = x + fontWidth - 1 < clipX2?x + fontWidth - 1:clipX2;
	s16 startY = y > clipY1?y:clipY1;
	s16 endY = y + fontHeight - 1 < clipY2?y + fontHeight - 1:clipY2;

	// Calculate clipping starting offsets - tells us the pixel offset
	// from the top-left of the character that we start drawing from
	u16 offsetStartX = startX - x;
	u16 offsetStartY = startY - y;

	// Calculate dimensions of region to copy
	u16 clipWidth = endX - startX + 1;
	u16 clipHeight = endY - startY + 1;

	// Ensure region to be drawn does not exceed the size of the character
	// this should never happen with clipX1<= clipX2 and fontWidth>0
	// if (clipWidth > fontWidth - offsetStartX) clipWidth = fontWidth - offsetStartX;
	// if (clipHeight > fontHeight - offsetStartY) clipHeight = fontHeight - offsetStartY;

	// Abort if there is no copying to be done
	if(!clipWidth || !clipHeight) return output;

	// Copy the pixel data
	u8 grayLevel;
	u8 * src = _sbit->buffer;
	u16 rgb;
	u16 bitmapColour;

	// This is the colour of the font
	u16 sourceColour = colour;
	u16 r = sourceColour & 0x7C00;
	u16 g = sourceColour & 0x03E0;
	u16 b = sourceColour & 0x001F;

	for(int pY = 0;pY < clipHeight; ++pY){
		for(int pX = 0;pX < clipWidth; ++pX){
			grayLevel = *(src + (offsetStartY + pY) * fontWidth + offsetStartX + pX);
			if(grayLevel > 0){
				if(grayLevel == maxGrays){
					// We just need to copy the few opaque pixels
					bitmap->setPixel(startX + pX,startY + pY,sourceColour);
				}else{
					// Antialiasing for the many gray pixels
					// We get the existing colour
					bitmapColour = bitmap->getPixel(startX + pX,startY + pY);

					u32 jump = transparencyJump[grayLevel];
					rgb = bitmapColour;

					s16 increment = r - (bitmapColour & 0x7C00);
					if(increment >= 0){
						rgb += (((increment * grayLevel) >> 8) & 0x7C00);
						rgb += (jump & BIT(increment))? 0x0400: 0;
					}else{
						rgb -= (((-increment * grayLevel) >> 8) & 0x7C00);
						rgb -= (jump & BIT(-increment))? 0x0400: 0;
					}

					increment = g - (bitmapColour & 0x03E0);
					if(increment >= 0){
						rgb += (((increment * grayLevel) >> 8) & 0x03E0);
						rgb += (jump & BIT(increment))? 0x0020: 0;
					}else{
						rgb -= (((-increment * grayLevel)>>8) & 0x03E0);
						rgb -= (jump & BIT(-increment))? 0x0020: 0;
					}

					increment = b - (bitmapColour & 0x001F);
					if(increment >= 0){
						rgb += (((increment * grayLevel) >> 8) & 0x001F);
						rgb += (jump & BIT(increment))? 0x0001: 0;
					}else{
						rgb -= (((-increment * grayLevel) >> 8) & 0x001F);
						rgb -= (jump & BIT(-increment))? 0x0001: 0;
					}

					bitmap->setPixel(startX + pX,startY + pY,rgb);
				}
			}
		}
	}

	return output;
}

bool FreeTypeFont::setFont(u8 width,u8 height,const FaceId* faceId){
	if((!faceId) || (!faceId->isValid())) return false; // look into this
	_scaler.width = (FT_UInt) width;
	_scaler.height = (FT_UInt) height;

	_scaler.face_id = (FTC_FaceID) faceId;
	_codepoint = 0xFFFFFFFF;
	return true;
}
